package com.utils; /**
 * Created by ithink on 18/5/8.
 */

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.utils.vo.MatchImageVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import sun.misc.BASE64Encoder;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 阿里云 - 图像搜索
 * 用户输入图片，可以快速在图片库中检索到与输入图片相似的图片集合
 */
@Slf4j
public class ImageSearchUtils {

    private final static String CHARSET_UTF8 = "utf8";
    private final static String ALGORITHM = "UTF-8";

    private static final String accessKeyId = AliyunConfig.getAccessKeyId();
    private static final String keySecrect = AliyunConfig.getAccessKeySecret();

    private static final String instanceName = AliyunConfig.getInstanceName();

    private static final String domain = "imagesearch.cn-shanghai.aliyuncs.com";

    /**
     * 添加图片
     * @param barCode
     * @param picName
     * @param imagePath
     * @throws Exception
     */
    public static void addImage(String barCode, String picName, String imagePath) throws Exception {

        String base64Str = getBase64Str(imagePath);

        List<NameValuePair> params = new ArrayList<>(4);
        params.add(new BasicNameValuePair("InstanceName", instanceName));
        params.add(new BasicNameValuePair("PicName", picName));
        params.add(new BasicNameValuePair("ProductId", barCode));
        params.add(new BasicNameValuePair("PicContent", base64Str));

        //log.warn("阿里云 - 图像搜索 - 添加图片 - 传入参数: "+"ProductId:{} and picName:{}", barCode, picName);

        //调用公共接口请求
        commonApiRequest("添加图片",  "add", params);
    }


    /**
     * 查询图片
     * @throws Exception
     */
    public static List<MatchImageVo> searchImage(byte[] imgBytes) throws Exception {
        //byte[] bytes2 = getBytes(imageUrl);
        //byte[] bytes2 = getImageFromNetByUrl(imageUrl);
        Base64 base64 = new Base64();

        List<NameValuePair> params = new ArrayList<>(4);
        params.add(new BasicNameValuePair("InstanceName", instanceName));
        params.add(new BasicNameValuePair("PicContent", base64.encodeToString(imgBytes)));

        //调用公共接口请求
        String result = commonApiRequest("查询图片",  "search", params);

        //解析数据
        if (StringUtils.isNotBlank(result)) {
            JSONArray jsonArray= (JSONArray) JSONArray.parseObject(result).get("Auctions");
            if (null != jsonArray) {
                return JSON.parseArray(jsonArray.toString(), MatchImageVo.class);
            }
        }

        return null;
    }

    /**
     * 删除图片
     * @param barCode
     * @param picName
     * @throws Exception
     */
    public static void deleteImage(String barCode, String picName) throws Exception {

        List<NameValuePair> params = new ArrayList<>(3);
        params.add(new BasicNameValuePair("InstanceName", instanceName));
        params.add(new BasicNameValuePair("PicName", picName));
        params.add(new BasicNameValuePair("ProductId", barCode));

        //log.info("阿里云 - 图像搜索 - 删除图片 - 传入参数: ProductId:{} and picName:{}", barCode, picName);

        //调用公共接口请求
        commonApiRequest("删除图片",  "delete", params);
    }

    /**
     *  公共接口请求
     * @param apiName
     * @param operation
     * @param params
     * @return
     * @throws Exception
     */
    private static String commonApiRequest(String apiName, String operation, List<NameValuePair> params) throws Exception {

        String result = null;

        try {
            HttpEntity entity = new UrlEncodedFormEntity(params, CHARSET_UTF8);
            String content = convert(entity.getContent(), StandardCharsets.UTF_8);

            Map<String, String> headers = new HashMap<>();
            String signatureStr = buildSignatureStr(operation, content, headers);

            byte[] signBytes = hmacSHA1Signature(keySecrect, signatureStr);
            String signature = newStringByBase64(signBytes);
            String authorization = "acs " + accessKeyId + ":" + signature;
            headers.put("authorization", authorization);

            String url = "http://" + domain + "/v2/image/"+operation;
            //log.warn("阿里云 - 图像搜索 - 认证信息: authorization:{} and url:{}", authorization, url);

            HttpPost httpPost = new HttpPost(url);
            for (String key : headers.keySet()) {
                httpPost.addHeader(key, headers.get(key));
            }

            httpPost.setEntity(entity);
            result = access(httpPost);
        } catch (ServerException e) {
            e.printStackTrace();
        }catch (
          ClientException e) {
            log.error("阿里云 - 图像搜索 - " + apiName + " - 失败: ErrMsg:{} and ErrCode:{} and RequestId:{} and detail:{}", e.getErrMsg(), e.getErrCode(), e.getRequestId(), e);
            return result;
        }

        log.warn("阿里云 - 图像搜索 - " + apiName + " - 成功: "+result);
        return result;
    }


    private static String getMd5(String body) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        BASE64Encoder base64en = new BASE64Encoder();
        return base64en.encode(md5.digest(body.getBytes(StandardCharsets.UTF_8)));
    }

    private static String getGMT() {
        Calendar cd = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("EEE d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        return sdf.format(cd.getTime());
    }


    private static String generateSignatureNonce() {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 32; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    private static String buildSignatureStr(String operation, String postContent, Map<String, String> headers) {
        String data = "POST\n";
        String accept = "application/json";
        data += accept + "\n";

        String contentMd5;
        try {
            contentMd5 = getMd5(postContent);
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            return null;
        }
        data += contentMd5 + "\n";

        String contentType = "application/x-www-form-urlencoded; charset=" + ALGORITHM;
        data += contentType + "\n";

        String gmt = getGMT();
        data += gmt + "\n";

        String method = "HMAC-SHA1";
        data += "x-acs-signature-method:" + method + "\n";

        String signatureNonce = generateSignatureNonce();
        data += "x-acs-signature-nonce:" + signatureNonce + "\n";

        String apiVersion = "2019-03-25";
        data += "x-acs-version:" + apiVersion + "\n";
        data += "/v2/image/" + operation;

        headers.put("x-acs-version", apiVersion);
        headers.put("x-acs-signature-method", method);
        headers.put("x-acs-signature-nonce", signatureNonce);

        headers.put("accept", accept);
        headers.put("content-md5", contentMd5);
        headers.put("content-type", contentType);
        headers.put("date", gmt);

        return data;
    }

    private static byte[] hmacSHA1Signature(String secret, String baseString) throws Exception {
        if (secret == null || secret.length() == 0) {
            throw new IOException("secret can not be empty");
        }
        if (baseString == null || baseString.length() == 0) {
            return null;
        }
        Mac mac = Mac.getInstance("HmacSHA1");
        SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        mac.init(keySpec);
        return mac.doFinal(baseString.getBytes(StandardCharsets.UTF_8));
    }

    private static String newStringByBase64(byte[] bytes)
        throws UnsupportedEncodingException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        return new String(Base64.encodeBase64(bytes, false), StandardCharsets.UTF_8);
    }

    private static String access(HttpRequestBase httpRequest) {
        CloseableHttpClient client = HttpClients.createDefault();
        HttpResponse response;
        String result;
        try {
            httpRequest.setHeader("accept-encoding", "");
            response = client.execute(httpRequest);
            if (response.getStatusLine().getStatusCode() == 200) {
                HttpEntity httpEntity = response.getEntity();
                result = EntityUtils.toString(httpEntity);
            } else {
                HttpEntity httpEntity = response.getEntity();
                result = EntityUtils.toString(httpEntity);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        return result;
    }

    private static String convert(InputStream inputStream, Charset charset) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        String line;
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, charset))) {
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
        }
        return stringBuilder.toString();
    }

    private static byte[] getBytes(String filePath) {
        byte[] buffer = null;
        try {
            File file = new File(filePath);
            FileInputStream fis = new FileInputStream(file);
            // picture max size is 2MB
            ByteArrayOutputStream bos = new ByteArrayOutputStream(2000 * 1024);
            byte[] b = new byte[1000];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            fis.close();
            bos.close();
            buffer = bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer;
    }

    /**
     * 网络图片转换Base64的方法
     *
     * @param netImagePath
     */
    private static String netImageToBase64(String netImagePath) {
        final ByteArrayOutputStream data = new ByteArrayOutputStream();
        try {
            // 创建URL
            URL url = new URL(netImagePath);
            final byte[] by = new byte[1024];
            // 创建链接
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            InputStream is = conn.getInputStream();
            // 将内容读取内存中
            int len = -1;
            while ((len = is.read(by)) != -1) {
                data.write(by, 0, len);
            }
            // 对字节数组Base64编码
            BASE64Encoder encoder = new BASE64Encoder();
            //windows上将图片下载下来后有换行添加“replaceAll("\r\n", "")”为取消换行
            String  strNetImageToBase64 = encoder.encode(data.toByteArray()).replaceAll("\r\n", "");
            // 在发布到linux上时取消换行
            // String  strNetImageToBase64 = encoder.encode(data.toByteArray()).replaceAll("\n", "");
            // 关闭流
            is.close();
            data.close();
            return strNetImageToBase64;


        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 图片转换为Base64
     */
    private static String getBase64Str(String imagePath) {
        String base64Str = "";
        //网络URL
        if (-1 !=imagePath.indexOf("http")){
            base64Str = netImageToBase64(imagePath);
            //本地URL
        }else{
            byte[] bytes2 = getBytes(imagePath);
            Base64 base64 = new Base64();
            base64Str = base64.encodeToString(bytes2);
        }
        return base64Str;
    }

    /**
     * 根据地址获得数据的字节流
     * @param strUrl 网络连接地址
     * @return byte[]
     */
    public static byte[] getImageFromNetByUrl(String strUrl){
        try {
            URL url = new URL(strUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            //设置超时间为10秒
            conn.setConnectTimeout(10 * 1000);
            //防止屏蔽程序抓取而返回403错误
            conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
            //得到输入流
            InputStream inStream = conn.getInputStream();
            byte[] btImg = readInputStream(inStream);//得到图片的二进制数据
            return btImg;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 从输入流中获取数据
     * @param inStream 输入流
     * @return byte[]
     * @throws Exception
     */
    public static byte[] readInputStream(InputStream inStream) throws Exception{
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1000];
        int len;
        while( (len=inStream.read(buffer)) != -1 ){
            outStream.write(buffer, 0, len);
        }
        inStream.close();
        return outStream.toByteArray();
    }

    /**
     * 测试
     * @param args
     * @throws Exception
     */
   /* public static void main(String[] args) throws Exception {
        if (accessKeyId.length() == 0 || keySecrect.length() == 0 || instanceName.length() == 0) {
            System.err.println("accessKeyId accessKeySecret and instanceName need specify.");
            return;
        }
        //addImage();
        //searchImage();
        //deleteImage("620973 TIVD4 1000", "40956");
    }*/
}
